<template>
  <div id="app" @click="onClick">
    <i id="bubbleradius"></i>
    <span
      v-for="c in circles"
      :style="style(c)"
      :key="c.key"
      :class="{ popped: c.popped }"
      >&nbsp;</span
    >
  </div>
</template>

<script>
var globalID = null;
const rotate = (x, y, sin, cos, reverse) => {
  return reverse
    ? {
        x: cos * x + sin * y,
        y: cos * y - sin * x,
      }
    : {
        x: cos * x - sin * y,
        y: cos * y + sin * x,
      };
};
const flatten = (arr) =>
  arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
export default {
  data() {
    return {
      circles: [],
      lastExec: null,
      hue: 250,
      lastCollisions: [],
      hiddenProperty: null,
      visibilityChangeEvent: null,
      moving: true,
    };
  },
  computed: {},
  methods: {
    style(c) {
      return `top:${c.y}px;left:${c.x}px;box-shadow:0 0 2rem hsl(${c.hue}, 75%, 50%) inset`;
    },
    update(tm) {
      if (!this.moving) {
        return;
      }
      if (this.lastExec && this.circles.length) {
        var diff = tm - this.lastExec;
        // var huediff = (this.hue + diff/30) % 360
        var box = this.$el.getBoundingClientRect();
        var radius = this.$el
          .querySelector("#bubbleradius")
          .getBoundingClientRect().width;

        var couples = [];
        this.circles
          .filter((cc) => !cc.popped)
          .forEach((c1) => {
            this.circles
              .filter((cc) => !cc.popped)
              .forEach((c2) => {
                if (c1 !== c2) {
                  couples.push([c1, c2]);
                }
              });
          });

        var collisions = couples.filter((couple) => {
          var dist = Math.sqrt(
            Math.pow(couple[0].y - couple[1].y, 2) +
              Math.pow(couple[0].x - couple[1].x, 2)
          );
          return dist < radius * 2;
        });

        var newcollisions = collisions.filter((couple) => {
          var key = couple[0].key + couple[1].key;
          return this.lastCollisions.indexOf(key) < 0;
        });

        newcollisions.forEach((couple) => {
          var a = couple[0];
          var b = couple[1];

          if (a.collisionFree && b.collisionFree) {
            // if (false) {
              // a.new_vx =
              //   (a.vx * (a.mass - b.mass) + 2 * b.mass * b.vx) /
              //   (a.mass + b.mass);
              // a.new_vy =
              //   (a.vy * (a.mass - b.mass) + 2 * b.mass * b.vy) /
              //   (a.mass + b.mass);
            // } else {
              var dx = b.x - a.x;
              var dy = b.y - a.y;
              var collisionAngle = Math.atan2(dy, dx);
              var sin = Math.sin(collisionAngle);
              var cos = Math.cos(collisionAngle);
              var pos_a = { x: 0, y: 0 };
              var pos_b = rotate(dx, dy, sin, cos, true);
              var vel_a = rotate(a.vx, a.vy, sin, cos, true);
              var vel_b = rotate(b.vx, b.vy, sin, cos, true);
              var vxTotal = vel_a.x - vel_b.x;
              vel_a.x =
                ((a.mass - b.mass) * vel_a.x + 2 * b.mass * vel_b.x) /
                (a.mass + b.mass);
              vel_b.x = vxTotal + vel_a.x;
              pos_a.x += vel_a.x;
              pos_b.x += vel_b.x;

              var pos_a_final = rotate(pos_a.x, pos_a.y, sin, cos, false);
              var vel_a_final = rotate(vel_a.x, vel_a.y, sin, cos, false);
              a.new_x = a.x + pos_a_final.x;
              a.new_y = a.y + pos_a_final.y;
              a.new_vx = vel_a_final.x;
              a.new_vy = vel_a_final.y;

              // each collision shows up in array twice: a->b and b->a
              // var pos_b_final = rotate(pos_b.x, pos_b.y, sin, cos, false)
              // var vel_b_final = rotate(vel_b.x, vel_b.y, sin, cos, false)
              // b.x = a.x + pos_b_final.x
              // b.y = a.y + pos_b_final.y
              // b.new_vx = vel_b_final.x;
              // b.new_vy = vel_b_final.y;
            // }
          }
        });

        newcollisions.forEach((couple) => {
          var a = couple[0];
          // var b = couple[1];
          if (a.new_vy) {
            a.vx = a.new_vx;
            a.vy = a.new_vy;
            a.x = a.new_x;
            a.y = a.new_y;
            a.hue += 20;
          }
        });

        this.lastCollisions = collisions.map((couple) => {
          return couple[0].key + couple[1].key;
        });

        var collided = [...new Set(flatten(newcollisions))];
        var collidedKeys = collided.map((c) => c.key);

        this.circles.forEach((c) => {
          c.collisionFree = c.collisionFree || collidedKeys.indexOf(c.key) < 0;
          if (c.y < 0) {
            c.vy = Math.abs(c.vy);
          } else if (c.y > box.height) {
            c.vy = -Math.abs(c.vy);
          }
          if (c.x < 0) {
            c.vx = Math.abs(c.vx);
          } else if (c.x > box.width) {
            c.vx = -Math.abs(c.vx);
          }

          c.y += c.vy * diff;
          c.x += c.vx * diff;
        });
      }
      this.lastExec = tm;
      globalID = requestAnimationFrame(this.update);
    },
    handleVisibilityChange() {
      if (!document[this.hiddenProperty]) {
        globalID = requestAnimationFrame(this.update);
        this.moving = true;
      } else {
        cancelAnimationFrame(globalID);
        this.moving = false;
      }
    },
    onClick(evt) {
      let m = {
        x: evt.pageX,
        y: evt.pageY,
      };
      let nearest = null;
      let nearest_distsq = Infinity;
      let dx, dy, distsq;
      // var radius = this.$el
      //   .querySelector("#bubbleradius")
      //   .getBoundingClientRect().width;
      this.circles.forEach((c) => {
        dx = m.x - c.x;
        dy = m.y - c.y;
        distsq = dx * dx + dy * dy;
        if (distsq < nearest_distsq && distsq < c.radius * c.radius) {
          nearest = c;
          nearest_distsq = distsq;
        }
      });
      if (nearest) {
        // let idx = this.circles.indexOf(nearest)
        // this.circles.splice(idx, 1)
        nearest.popped = true;
      }
    },
  },
  mounted() {
    var box = this.$el.getBoundingClientRect();
    var radius = this.$el
      .querySelector("#bubbleradius")
      .getBoundingClientRect().width;
    var max = (box.width * box.height) / 300 / Math.pow(radius, 1.2);
    for (var i = 0; i < max; i++) {
      this.circles.push({
        key: Math.random(),
        y: Math.random() * box.height,
        x: Math.random() * box.width,
        vx: Math.random() / 5,
        vy: Math.random() / 5,
        hue: Math.random() * 360,
        collisionFree: false,
        mass: 1,
        radius: radius,
        popped: false,
      });
    }
    globalID = requestAnimationFrame(this.update);

    var hidden, visibilityChange;
    if (typeof document.hidden !== "undefined") {
      // Opera 12.10 and Firefox 18 and later support
      hidden = "hidden";
      visibilityChange = "visibilitychange";
    } else if (typeof document.msHidden !== "undefined") {
      hidden = "msHidden";
      visibilityChange = "msvisibilitychange";
    } else if (typeof document.webkitHidden !== "undefined") {
      hidden = "webkitHidden";
      visibilityChange = "webkitvisibilitychange";
    }
    this.hiddenProperty = hidden;
    this.visibilityChangeEvent = visibilityChange;
    document.addEventListener(
      this.visibilityChangeEvent,
      this.handleVisibilityChange,
      false
    );
  },
  beforeUnmount() {
    document.removeEventListener(
      this.visibilityChangeEvent,
      this.handleVisibilityChange,
      false
    );
  },
};
</script>

<style lang="scss" scoped>
* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
  user-select: none;
}

#bubbleradius {
  width: 4.5rem;
  height: 1px;
  opacity: 0;
  z-index: -1;
  display: block;
}

body {
  overflow: hidden;
}

#app {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: #1d1e22;
  color: white;
}

span {
  transform: translate(-50%, -50%);
  border-radius: 9rem;
  height: 9rem;
  width: 9rem;
  display: block;
  position: absolute;
  opacity: 1;
  transition: box-shadow 0.5s ease-in-out, transform 0.07s ease-out,
    opacity 0.04s ease-in;
}
span.popped {
  transform: translate(-50%, -50%) scale(2);
  opacity: 0;
}
span:after {
  content: "";
  position: absolute;
  top: 18%;
  left: 18%;
  background-color: rgba(191, 255, 255, 0.6);
  width: 0.6428571429rem;
  height: 1.5rem;
  border-radius: 50%;
  transform: rotate(45deg) scale(0.8);
}
</style>
